/*
 * Copyright (c) 2007 jMouse Gestures
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMouse Gestures' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jmg.gesture;

import com.jmg.gesture.checker.PercentMatchChecker;
import com.jmg.gesture.checker.GesturePerformedChecker;
import com.jmg.mouse.MouseMovement;
import java.io.Serializable;
import java.util.Arrays;


/**
 * Gesture class holds the mapping of 
 * {@link com.jmg.gesture.GestureMovement} into a valid mouse gesture.
 * <p>
 * This class is completely immutable and all property values must be set at
 * construction time.
 * </p>
 * 
 * @author  Christopher Martin
 * @version $Id: Gesture.java 17 2007-09-28 15:00:09Z GentlemanHal $
 */
public class Gesture implements  
    Serializable 
{
    /** Serial Version UID */
    private static final long serialVersionUID = 1L;
    
    private String name;
    private GestureMovement[] movements;
    private GesturePerformedChecker checker;
    
    
    

    
    /**
     * Creates a new instance of <code>Gesture</code>.  Instances of this class
     * are immutable.
     * 
     * @param  movements The movements that are required to perform this gesture.
     *         The order the movements are passed into this constructor is the
     *         order in which the movements must be performed.
     * @throws com.jmg.gesture.MappingNotSetException If a <code>null</code> 
     *         argument is passed for movements or if zero movements are passed.
     */
    public Gesture(final GestureMovement ... movements) 
        throws MappingNotSetException
    {
        this(null, movements);
    }
    
    /**
     * Creates a new instance of <code>Gesture</code>.  Instances of this class
     * are immutable.
     * 
     * @param  name The name of this <code>Gesture</code>, the idea of naming
     *         <code>Gestures</code> is so that they can be uniquely identified.
     *         Therefore it is recommended that not only all gestures be named
     *         but they each have a unique name.  If <code>null</code> is
     *         passed then the name &quot;Gesture&quot; is set.
     * @param  movements The movements that are required to perform this gesture.
     *         The order the movements are passed into this constructor is the
     *         order in which the movements must be performed.
     * @throws com.jmg.gesture.MappingNotSetException If a <code>null</code> 
     *         argument is passed for movements or if zero movements are passed.
     */
    public Gesture(final String name, final GestureMovement ... movements) 
        throws MappingNotSetException
    {
        this(name, new PercentMatchChecker(), movements);
    }
    
    /**
     * Creates a new instance of <code>Gesture</code>.  Instances of this class
     * are immutable.
     * 
     * @param  name The name of this <code>Gesture</code>, the idea of naming
     *         <code>Gestures</code> is so that they can be uniquely identified.
     *         Therefore it is recommended that not only all gestures be named
     *         but they each have a unique name.  If <code>null</code> is
     *         passed then the name &quot;Gesture&quot; is set.
     * @param  checker The checker to use for checking if a valid gesture is
     *         performed or not.
     *         <p>
     *         Note:<br />
     *         If this class is not serializable then it will stop this gesture
     *         from being serialised.
     *         </p>
     * @param  movements The movements that are required to perform this gesture.
     *         The order the movements are passed into this constructor is the
     *         order in which the movements must be performed.
     * @throws com.jmg.gesture.MappingNotSetException If a <code>null</code> 
     *         argument is passed for movements or if zero movements are passed.
     * @throws java.lang.IllegalArgumentException If a <code>null</code>
     *         argument is passed for checker.
     */
    public Gesture(final String name, final GesturePerformedChecker checker, 
                   final GestureMovement ... movements) 
        throws MappingNotSetException, IllegalArgumentException
    {
        if (null == movements || 0 == movements.length) {
            throw new MappingNotSetException("Movements must be set! " + movements);
        }
        if (null == checker) {
            throw new IllegalArgumentException("checker was null!");
        }
            
        
        this.name      = null == name ? "Gesture" : name;
        this.movements = movements.clone();
        this.checker   = checker;
    }
    
    
    
    
    
    /**
     * Gets the name of this <code>Gesture</code> or 
     * <code>&quot;Gesture&quot;</code> if this gesture was not named.
     * <p>
     * This method will not return <code>null</code> but it may return an 
     * empty string &quot;&quot;.
     * </p>
     *
     * @return The name.
     */
    public String getName() {
        return name;
    }
    
    /**
     * Gets the movements of this <code>Gesture</code>, this method will never
     * return <code>null</code> as valid movements must be set at construction
     * time.
     * <p>
     * A clone of the actual movement array is returned by this method to
     * ensure the actual movements can not be changed.
     * </p>
     * 
     * @return The <code>GestureMovements</code>.
     */
    public GestureMovement[] getMovements() {
        return movements.clone();
    }
    
    /**
     * Gets a particular movement specified by the given index.  The index
     * should be between <code>0</code> and 
     * {@link #getNumberOfDirections} <code> - 1</code>.
     * 
     * @param  index The index of the movement to return.
     * @throws java.lang.IllegalArgumentException If index is out of range.
     * @return The <code>GestureMovement</code>.
     */
    public GestureMovement getMovement(int index) 
        throws IllegalArgumentException
    {
        if (index < 0 || index >= getNumberOfDirections()) {
            throw new IllegalArgumentException("index out of range: " + index);
        }
        
        
        return movements[index];
    }
    
    /**
     * Gets the number of directions required to perform this
     * <code>Gesture</code>.
     *
     * @return The number of directions.
     */
    public int getNumberOfDirections() {
        return movements.length;
    }
    
    /**
     * Returns the <code>GesturePerformedChecker</code> that is used
     * to check when this gesture is performed.
     * 
     * @return The checker.
     */
    public GesturePerformedChecker getChecker() {
        return checker;
    }
    
    
    

    
    /**
     * Tests the given <code>MouseMovements</code> against this 
     * <code>Gesture</code> to see if they are valid or not.
     * <p>
     * This method actually just delegates to this gestures
     * {@link #getChecker checker}.
     * </p>
     * 
     * @param  mouseMovements The <code>MouseMovements</code> to check.
     * @return <code>true</code> if they are valid, <code>false</code> otherwise.
     */
    public boolean validGesture(MouseMovement ... mouseMovements) {
        return checker.matched(this, mouseMovements);
    }

    
    
    
    
    /**
     * Returns a <code>String</code> representation of this <code>Gesture</code>.
     * 
     * @return A <code>String</code> representation.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder(50);
        
        buf.append(getClass().getName());
        buf.append("[name=").append(name);
        buf.append(",mappings=").append(Arrays.toString(movements));
        buf.append(']');
        
        return buf.toString();
    }

    /**
     * Indicates whether some other <code>Object</code> is equal to this one.
     * 
     * @param  obj The reference <code>Object</code> with which to compare.
     * @return <code>true</code> if this is the same as the obj argument,
     *         <code>false</code> otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        
        if (null == obj || obj.getClass() != getClass()) {
            return false;
        }
            
        Gesture g = (Gesture) obj;

        return name.equals(g.name) && Arrays.equals(g.movements, movements);
    }

    /**
     * Returns a hash code value for this <code>Gesture</code>.
     * 
     * @return A hash code value for this <code>Gesture</code>.
     */
    @Override
    public int hashCode() {
        return name.hashCode()                * 13 + 
               Arrays.deepHashCode(movements) * 13;
    }
    
}
